【朗報】TerraformのCloudFromation用リソース/データソースでYAMLに対応しそう
はじめに
こんにちは、中山です。
TerraformのPRを眺めていたら、個人的に待ち望んでいた aws_cloudformation_stack
データソースと aws_cloudformation_stack
リソースのYAML対応用PRが上がっていたのでご紹介したいと思います。以前TerraformとCloudFromation(以下CFn)を連携させるエントリを書きましたが、その時点ではJSONで書かなければならずちょっと使いにくいという問題がありました。
執筆時点(2017/01/18)ではまだアップストリームにマージされていませんが、今後このPRが取り込まれたらJSON業とさよならできそうですね!
使ってみる
それでは早速使ってみましょう。
インストール
上述したようにこのPRはまだTerraformのアップストリームに取り込まれていません。PR作者の方がフォークしたリポジトリからTerraformのバイナリをコンパイルしましょう。
$ cd $GOPATH/src/github.com/hashicorp/terraform/ $ git remote -v origin https://github.com/hashicorp/terraform (fetch) origin https://github.com/hashicorp/terraform (push) $ git remote add ordinaryexperts/terraform git@github.com:ordinaryexperts/terraform.git $ git remote -v ordinaryexperts/terraform git@github.com:ordinaryexperts/terraform.git (fetch) ordinaryexperts/terraform git@github.com:ordinaryexperts/terraform.git (push) origin https://github.com/hashicorp/terraform (fetch) origin https://github.com/hashicorp/terraform (push) $ git fetch ordinaryexperts/terraform <snip> $ git checkout feature/aws-cloudformation-yaml-support Branch feature/aws-cloudformation-yaml-support set up to track remote branch feature/aws-cloudformation-yaml-support from fork. Switched to a new branch 'feature/aws-cloudformation-yaml-support' $ git log -1 commit 5977eaa23633437aa6f9d8e3c63b37ea55b849a0 Author: Dylan Vaughn <dylancvaughn@gmail.com> Date: Mon Jan 9 13:59:11 2017 -0800 don't normalize YAML templates $ make dev <snip> $ $GOPATH/bin/terraform version Terraform v0.8.3-dev (5977eaa23633437aa6f9d8e3c63b37ea55b849a0) Your version of Terraform is out of date! The latest version is 0.8.4. You can update by downloading from www.terraform.io
コード
コンパイルが完了したら早速使ってみましょう。今回はみんな大好きLambda-Backed Custom Resourceを定義したテンプレートを aws_cloudformation_stack
リソースで作成し、 aws_cloudformation_stack
データソースで出力された値をTerraformで取得してみたいと思います。
main.tf
provider "aws" { region = "ap-northeast-1" } resource "aws_cloudformation_stack" "test" { name = "test-stack" template_body = "${file("${path.module}/template.yml")}" capabilities = ["CAPABILITY_IAM"] parameters { Name = "amzn-ami-hvm-*" } } data "aws_cloudformation_stack" "test" { name = "test-stack" } output "ami_id" { value = "${data.aws_cloudformation_stack.test.outputs["AMIId"]}" }
CFnのテンプレートはTerraformと分けたいので file
関数で読み込む形にしています。後述しますが、テンプレートでIAM Roleを作成しているため、 CAPABILITY_IAM
を設定しています。また、パラメータとして文字列を指定できるようにしました。 aws_cloudformation_stack
データソースでは先程作成したスタックからアウトプットで出力された内容(AMI Id)を出力しています。もちろん aws_cloudformation_stack
リソースの属性から参照することも可能です。今回はリソース/データソース両方共使いたかったのでこういった形にしています。
template.yml
AWSTemplateFormatVersion : 2010-09-09 Description: Test Template Parameters: Name: Description: Input Name Type: String Resources: LambdaAMIInfoRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: LambdaBasicExecRole Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DescribeImagePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ec2:DescribeImages Resource: "*" LambdaAMIInfo: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import boto3 import cfnresponse def handler(event, context): if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) response_data = {} filters = [ {'Name': 'architecture', 'Values': ['x86_64']}, {'Name': 'root-device-type', 'Values': ['ebs']}, {'Name': 'name', 'Values': [event['ResourceProperties']['Name']]}, {'Name': 'virtualization-type', 'Values': ['hvm']}, {'Name': 'block-device-mapping.volume-type', 'Values': ['gp2']}] try: images = boto3.client('ec2').describe_images(Owners=['amazon'], Filters=filters) except Exception as e: response_data['Error'] = e cfnresponse.send(event, context, cfnresponse.FAILED, response_data) for i in sorted([image for image in images['Images']], key=lambda x: x['Name']): if i['Name'].lower().count('beta') > 0 or i['Name'].lower().count('.rc') > 0: continue response_data['Id'] = i['ImageId'] cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data) Handler: index.handler Role: !GetAtt LambdaAMIInfoRole.Arn Runtime: python2.7 Timeout: 20 CustomAMIInfo: Type: Custom::AMIInfo Version: 1.0 Properties: ServiceToken: !GetAtt LambdaAMIInfo.Arn Name: !Ref Name Outputs: AMIId: Value: !GetAtt CustomAMIInfo.Id
今回のCFnテンプレートはLambda-Backed Custom Resourceでパラメータで渡された文字列を元にAMI Idを検索するという内容にしました。まぁ、aws_ami データソース使えば同等のことはできるのですが。。。アウトプットで取得したAMI Idを出力して、Terraformからその値を参照可能にしています。
実行
Terraform実行時点ではテスト用スタックが作成されていないので -target
オプションで特定のリソースのみ作成します。
$ $GOPATH/bin/terraform plan -target=aws_cloudformation_stack.test $ $GOPATH/bin/terraform apply -target=aws_cloudformation_stack.test aws_cloudformation_stack.test: Creating... capabilities.#: "" => "1" capabilities.1328347040: "" => "CAPABILITY_IAM" name: "" => "test-stack" outputs.%: "" => "<computed>" parameters.%: "" => "1" parameters.Name: "" => "amzn-ami-hvm-*" policy_body: "" => "<computed>" <snip> Apply complete! Resources: 1 added, 0 changed, 0 destroyed. The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command. State path: terraform.tfstate
上記のようにTerraformの作成が完了したら、 -target
オプション抜きで全てのコードを実行します。
$ $GOPATH/bin/terraform plan $ $GOPATH/bin/terraform apply aws_cloudformation_stack.test: Refreshing state... (ID: arn:aws:cloudformation:ap-northeast-1:/754159e0-dd53-11e6-8f47-503a369c8836) data.aws_cloudformation_stack.test: Refreshing state... Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: ami_id = ami-9f0c67f8
AWS CLIでもスタックの状態を確認してみます。
$ aws cloudformation describe-stacks \ --stack-name test-stack { "Stacks": [ { "StackId": "arn:aws:cloudformation:ap-northeast-1:************:stack/test-stack/754159e0-dd53-11e6-8f47-503a369c8836", "Description": "Test Template", "Parameters": [ { "ParameterValue": "amzn-ami-hvm-*", "ParameterKey": "Name" } ], "Tags": [], "Outputs": [ { "OutputKey": "AMIId", "OutputValue": "ami-9f0c67f8" } ], "CreationTime": "2017-01-18T07:55:20.076Z", "Capabilities": [ "CAPABILITY_IAM" ], "StackName": "test-stack", "NotificationARNs": [], "StackStatus": "CREATE_COMPLETE", "DisableRollback": false } ] }
正常にスタックが作成されているようです。やりましたね。
まとめ
いかがだったでしょうか。
TerraformからCFnを利用する際にYAMLで記述できるPRをご紹介しました。早くマージしてくれ!!!!1111
本エントリがみなさんの参考になれば幸いに思います。